﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Text;
using System.Threading;

using Common.Logging;


namespace SharpMail.Connections
{
    /// <summary>
    /// A connection manager that accepts TCP socket connections on a 
    /// background thread.
    /// </summary>
    public class DefaultConnectionManager : IConnectionManager
    {
        public event ConnectionHandler OnConnection;

        private ILog _log;
        private bool _shouldContinue;


        /// <summary>
        /// Constructs a new DefaultConnectionManager.
        /// </summary>
        public DefaultConnectionManager()
        {
            _log = LogManager.GetLogger(this.GetType());
            _shouldContinue = false;
        }


        /// <summary>
        /// Begins accepting TCP socket connections on background threads.
        /// </summary>
        /// <param name="ipEndPoint">The IP end point on which to listen for connections.</param>
        public void AcceptConnectionsOn(IPEndPoint ipEndPoint)
        {
            _log.Debug(string.Format("Enter AcceptConnectionsOn(ipEndPoint={0})", ipEndPoint.ToString()));

            _shouldContinue = true;
            TcpListener listener = new TcpListener(ipEndPoint);

            this.StartListener(listener);

            Thread listenerThread = new Thread(this.ListenTo);
            listenerThread.Name = string.Format("{0} Listener Thread", this.GetType().Name);
            listenerThread.Start(listener);

            _log.Debug("Leave AcceptConnectionsOn()");
        }

        /// <summary>
        /// Stops accepting new connections.  Any connections already accepted
        /// but not yet given to clients will still be given to clients.
        /// </summary>
        public void StopAcceptingConnections()
        {
            _shouldContinue = false;
        }


        // a convenience method to call listener.Start()
        private void StartListener(TcpListener listener)
        {
            try
            {
                listener.Start();
                _log.Info(string.Format("Now accepting connections on {0}.", listener.LocalEndpoint));
            }
            catch (SocketException se)
            {
                _log.Error("Failed to begin accepting connections.", se);
                throw;
            }
        }

        // the listener thread will die when this method returns
        private void ListenTo(object listenerObj)
        {
            _log.Debug(string.Format("Enter ListenTo(listenerObj={0})", listenerObj.ToString()));

            TcpListener listener = listenerObj as TcpListener;

            while (this._shouldContinue)
            {
                try
                {
                    // using the async methods here because it takes advantage 
                    // of the while loop.  It will wait for a new connection 
                    // for 250 milliseconds and then check if we should
                    // continue listening, which makes shutting down clean.
                    IAsyncResult result = listener.BeginAcceptTcpClient(
                        new AsyncCallback(this.listener_OnConnection), listener);
                    if (result.AsyncWaitHandle.WaitOne(250))
                    {
                        TcpClient client = listener.EndAcceptTcpClient(result);
                        _log.Info(string.Format("Accepted connection from {0}", client.Client.RemoteEndPoint));
                        this.DispatchConnection(client);
                    }
                }
                catch (Exception ex)
                {
                    _log.Error("Failed to handle potential connection.", ex);
                    // sleep for a short time to avoid an exception-filled hard loop
                    Thread.Sleep(250);
                }
            }

            _log.Info("Stopped listening for connections.");
            _log.Debug("Leave ListenTo()");
        }

        // just logs that a new connection is in progress.
        private void listener_OnConnection(IAsyncResult result)
        {
            _log.Debug("Receiving new connection.");
        }

        private void DispatchConnection(TcpClient connection)
        {
            if (null != this.OnConnection)
            {
                try
                {
                    this.OnConnection.BeginInvoke(new MessageUserAgent(connection), null, null);
                }
                catch (Exception ex)
                {
                    _log.Error("Failure while notifying clients of new connections", ex);
                }
            }
        }
    }
}
